#include "Agora.h"
#include "AudioOut.h"
#include "AudioIn.h"
#include "Empacter.h"
#include "SpeexEncoder.h"
#include "Buffer.h"
#include "Mutex.h"
#include "ThreadPool.h"
#include "Network.h"

Agora::Agora() :
_localTest(false),
_client(NULL),
_audioOut(NULL),
_checkBandwidth(true), // DEBUG BETA 2 -> STAT
_upload(0), // DEBUG BETA 2 -> STAT
_download(0), // DEBUG BETA 2 -> STAT
_uploadPerSec(0),
_downloadPerSec(0)
{
	ThreadPool::InitThreadPool(50);
	_bufReception = new Buffer(50000);
	_bufReception->SubscribeEnoughData(&Agora::ReceiveDataFromClient, 0, this);
	ThreadPool::Request *req = new ThreadPool::Request();

	req->autoFree = true;
	req->parameters = this;
	req->function = UpdateBandwidth;
	ThreadPool::GetThreadPool()->AddRequest(req);

}

Agora::~Agora()
{
}


void Agora::UpdateBandwidth(void* data)
{
	Agora* agora = (Agora*) data;
	while (agora->_checkBandwidth)
	{
		agora->_uploadPerSec = agora->_upload;
		agora->_downloadPerSec = agora->_download;
		agora->_upload = 0;
		agora->_download = 0;
		Sleep(1000);
	}
}

unsigned int Agora::GetUploadRate()
{
	return _uploadPerSec;
}

unsigned int Agora::GetDownloadRate()
{
	return _downloadPerSec;
}


int Agora::CreateDefaultMicro(FrequenceEnum frequence, ALFormat format)
{

	IBuffer *_bufIn = new Buffer(frequence);
	AudioIn *audioIn = new AudioIn(_bufIn, frequence);
	audioIn->AutoSelectInDevice(frequence, format);
	return this->AddAudioInput(audioIn, frequence);
}

void Agora::CreateDefaultEncoder(unsigned int quality, char headerValue)
{
	this->AddEncoder(new SpeexEncoder(quality), headerValue);
}

void Agora::CreateDefaultEmpacter()
{
	_empacter = new Empacter();
}

void Agora::CreateDefaultClient()
{
	_client = new Client(_empacter, _bufReception, 10000);
}

void Agora::CreateDefaultAudioOut()
{
	_audioOut = new AudioOut();
}


void Agora::SetLocalTest(bool state)
{
	this->_localTest = state;
}

bool Agora::GetLocalTest()
{
	return this->_localTest;
}

int	Agora::AddAudioInput(IAudioIn *audioIn, unsigned int frameSize)
{
	AudioInInfo *info = new AudioInInfo();

	info->audioIn = audioIn;
	audioIn->GetBuffer()->SubscribeEnoughData(&Agora::EncodeBuffer, frameSize / 2, this);
	info->bufferIn = audioIn->GetBuffer();
	info->bufferEmpacked = new Buffer(frameSize + sizeof(InfoBase) + sizeof(InfoAudioBase));
	info->bufferEncoded = new Buffer(frameSize);
	info->bufferEmpacked->SubscribeEnoughData(&Agora::SendBuffer, 0, this);
	info->bufferEncoded->SubscribeEnoughData(&Agora::EmpackedBuffer, 0, this);
	info->encoderKey = NONE;
	this->_audioInfoFromEmpacked[info->bufferEmpacked] = info;
	this->_audioInfoFromEncoded[info->bufferEncoded] = info;
	this->_audioInfoFromIn[info->bufferIn] = info;
	this->_audioIns.push_front(info);
	return (int)info;
}

void Agora::SetEncoderForAudioInput(char HeaderValue, int audioInputKey)
{
	bool notfound = true;
	AudioInInfo *info = (AudioInInfo*)audioInputKey;

	std::list<AudioInInfo*>::iterator it = this->_audioIns.begin();
	std::list<AudioInInfo*>::iterator end = this->_audioIns.end();
	for(;it != end; ++it)
	{
		if ((*it) == info)
		{
			notfound = false;
			break;
		}
	}
	if (notfound)
		return;
	info->encoderKey = HeaderValue;
}

void Agora::StartAudioInput(int audioInputKey)
{
	bool notfound = true;
	AudioInInfo *info = (AudioInInfo*)audioInputKey;

	std::list<AudioInInfo*>::iterator it = this->_audioIns.begin();
	std::list<AudioInInfo*>::iterator end = this->_audioIns.end();
	for(;it != end; ++it)
	{
		if ((*it) == info)
		{
			notfound = false;
			break;
		}
	}
	if (notfound)
		return;
	info->audioIn->Start();
}

void Agora::StopAudioInput()
{
	std::list<AudioInInfo*>::iterator it = this->_audioIns.begin();
	std::list<AudioInInfo*>::iterator end = this->_audioIns.end();
	for(;it != end; ++it)
	{
		(*it)->audioIn->Stop();

	}
}

void Agora::StartAudioInput()
{
	std::list<AudioInInfo*>::iterator it = this->_audioIns.begin();
	std::list<AudioInInfo*>::iterator end = this->_audioIns.end();
	for(;it != end; ++it)
	{
		(*it)->audioIn->Start();
	}
}

void Agora::StopAudioInput(int audioInputKey)
{
	bool notfound = true;
	AudioInInfo *info = (AudioInInfo*)audioInputKey;

	std::list<AudioInInfo*>::iterator it = this->_audioIns.begin();
	std::list<AudioInInfo*>::iterator end = this->_audioIns.end();
	for(;it != end; ++it)
	{
		if ((*it) == info)
		{
			notfound = false;
			break;
		}
	}
	if (notfound)
		return;
	info->audioIn->Stop();
}

void	Agora::RemoveAudioInput(int audioInputKey)
{
	bool notfound = true;
	AudioInInfo *info = (AudioInInfo*)audioInputKey;

	std::list<AudioInInfo*>::iterator it = this->_audioIns.begin();
	std::list<AudioInInfo*>::iterator end = this->_audioIns.end();
	for(;it != end; ++it)
	{
		if ((*it) == info)
		{
			notfound = false;
			break;
		}
	}
	if (notfound)
		return;
	info->audioIn->Stop();
	delete info->audioIn;
	this->_audioIns.remove(info);
	delete info->bufferEmpacked;
	delete info->bufferEncoded;
	delete info->bufferIn;
	this->_audioInfoFromEmpacked.erase(info->bufferEmpacked);
	this->_audioInfoFromEncoded.erase(info->bufferEncoded);
	this->_audioInfoFromIn.erase(info->bufferIn);
	delete info;
}

bool	Agora::IsConnected()
{
	return (this->_client->IsConnected());
}

void	Agora::Disconnect()
{
	if (Agora::IsConnected())
	{
		this->_client->Disconnect();
	}
	return ;
}

void Agora::AddEncoder(IEncoder *encoder, char headerValue)
{
	this->_encoders[headerValue] = new EncoderInfo();
	this->_encoders[headerValue]->mutex = new Mutex();
	this->_encoders[headerValue]->encoder = encoder;
}

void Agora::RemoveEncoder(char headerValue)
{
	if (this->_encoders.count(headerValue))
	{
		EncoderInfo* info = this->_encoders[headerValue]; 

		info->mutex->Lock();
		this->_encoders.erase(headerValue);
		delete info->encoder;
		info->mutex->Unlock();
		delete info->mutex;
		delete info;
	}
}

void Agora::SetEncoderQuality(char headerValue, unsigned int quality)
{
	if (this->_encoders.count(headerValue))
	{
		EncoderInfo* info = this->_encoders[headerValue]; 

		info->mutex->Lock();
		info->encoder->SetQuality(quality);
		info->mutex->Unlock();
	}
}


void Agora::SendBuffer(IBuffer* buff, void* data)
{
	Agora *agora = (Agora*)(data);;
	int size = buff->Size();

	agora->_upload += size;
	if (!agora->_localTest)
		agora->_client->SendBuffer(buff->Read(size, false),(unsigned int)size);
	else
		agora->_bufReception->Write(buff->Read(size, false), size);
	buff->Delete(size);
}

void Agora::EmpackedBuffer(IBuffer* buff, void* data)
{
	Agora *agora = (Agora*)(data);

	if (!agora->_localTest && agora->_client->GetId() < 0)
	{
		buff->Clean();
		return;
	}
	agora->_empacter->SetId(agora->_client->GetId());
	InfoAudioBase info;
	AudioInInfo *tmp = agora->_audioInfoFromEncoded[buff];
	unsigned int size = buff->Size();
	info.frequence = tmp->audioIn->GetFrequence();
	info.format = tmp->audioIn->GetFormat();
	info.spacial = false;
	info.encodage = agora->_encoders.count(tmp->encoderKey) ? tmp->encoderKey : NONE;
	agora->_empacter->EmpactAudio(buff, tmp->bufferEmpacked, size, &info);
}

void Agora::EncodeBuffer(IBuffer* buff, void* data)
{
	Agora *agora = (Agora*)(data);
	AudioInInfo* info = agora->_audioInfoFromIn[buff];
	int size = buff->Size();

	if (info->encoderKey != NONE && agora->_encoders.count(info->encoderKey))
	{
		agora->_encoders[info->encoderKey]->mutex->Lock();
		agora->_encoders[info->encoderKey]->encoder->Encode(buff, info->bufferEncoded, info->audioIn->GetFrequence());
		agora->_encoders[info->encoderKey]->mutex->Unlock();
	}
	else
	{
		info->bufferEncoded->Write(buff->Read(size, false), size);
		buff->Delete(size);
	}
}

void Agora::ReceiveDataFromClient(IBuffer* buff, void *_agora)
{
	static int i = 0;
	Agora *agora = reinterpret_cast<Agora *>(_agora);
	Header head;
	void *data;
	unsigned int size = buff->Size();

	if (!agora->_empacter)
	{
		buff->Clean();
		return;
	}
	while ((data = agora->_empacter->Desempact(buff->Read(size, false), size, &head)))
	{
		agora->_download += size;
		if (head.base->type == AUDIO)
		{

			if (!agora->_audioOut || !agora->_audioOut->IsRunning())
			{
				buff->Delete(size);
				size = buff->Size();
				continue;
			}
			if (agora->_bufSources.count(head.base->id) == 0)
			{
				agora->_bufSources[head.base->id] = new Buffer(50000);
				agora->_audioOut->AddSource(agora->_bufSources[head.base->id], &head);
			}
			agora->_audioOut->UpdateSourceConfiguration(agora->_bufSources[head.base->id], &head);
			if (head.audio->encodage == NONE)
			{
				agora->_bufSources[head.base->id]->Write(data, head.base->sizeData);
			}
			else if (agora->_encoders.count(head.audio->encodage))
			{
				agora->_encoders[head.audio->encodage]->mutex->Lock();
				agora->_encoders[head.audio->encodage]->encoder->Decode(data, head.base->sizeData, agora->_bufSources[head.base->id], head.audio->frequence);
				agora->_encoders[head.audio->encodage]->mutex->Unlock();
			}
		}
		buff->Delete(size);
		size = buff->Size();
	}
}

int Agora::Connect(std::string adress, int port)
{
	Network::AgoraNetworkInitialize();
	return this->_client->Connect(adress, port);
}



void Agora::StartAudioOut()
{
	if (_audioOut)
		_audioOut->Start();
}

void Agora::StopAudioOut()
{
	if (_audioOut)
		_audioOut->Stop();
}